Linguaggio C |
||
per collaborazioni, commenti, critiche, e altro contattateci alla e-mail: clubinfo@libero.it risponderemo al più presto! |
Pagina principale | Lezione precedente | Lezione successiva
I tipi di dato che abbiamo visto finora sono intrinseci al
compilatore: sono, cioè, dei tipi di dato che il compilatore è in grado di
gestire senza ulteriori costruzioni logiche da parte del programmatore. Per
questa ragione vengono chiamati "tipi elementari".
Questi, però, spesso non sono sufficienti a rappresentare in modo esauriente un
gruppo di dati diversi collegati tra loro, ad esempio i vari dati disomogenei
che compongono un indirizzo: l'array di caratteri che contiene il nome, quello
che contiene il cognome, un longint che contiene il numero di telefono ecc.
Il linguaggio C mette a disposizione del programmatore alcuni strumenti per rappresentare entità complesse dando la possibilità di definire veri e propri tipi di dato "nuovi" (o quasi) e di gestirli come se fossero intrinseci al compilatore.
Tra gli strumenti cui si ho fatto cenno prima, il più importante è la struttura , mediante la quale si definisce un modello che individua un'aggregazione di tipi di dato fondamentali.
La parola chiave per la dichiarazione di una struttura è "struct" e gli elementi di una struttura possono essere di qualsiasi tipo di dati valido in C.
struct indirizzo {
char nome[30];
char via[60];
char citta[20];
char stato[2];
int cap;
long int telefono;
};
Le cose da notare sono due: la prima è che all'interno della struttura ci possono essere tipi di dati diversi (array char, int, ecc.). La seconda è la sintassi: si mette un nome a piacere dopo "struct", poi si apre la parentesi { graffa, si immettono gli elementi terminati ognuno col punto e virgola, infine si chiude la parentesi graffa e si mette un punto e virgola finale.
Comunque questa dichiarazione (in questa forma: più avanti vedremo altre forme di dichiarazione che, invece, lo fanno) non comporta che il compilatore riservi dello spazio di memoria per l’allocazione dei campi della struttura stessa poiché essa non genera le variabili, ma la "forma" della struttura chiamata "indirizzo", cioè il suo modello.
Ossia definiamo quel "nuovo" tipo di dati complesso, astrattamente.
Per creare fisicamente una struttura del tipo descritto e poterlo utilizzare, occorre dichiararlo in maniera del tutto analoga a quanto avviene con le care vecchie variabili:
struct indirizzo IndirizzoCliente;
Soltanto ora abbiamo creato un esemplare della struttura
indirizzo, che risponde al nome
IndirizzoCliente pronta per essere usata con questo nome all’interno del
programma.
All’atto dell’istanziazione della nostra struttura il compilatore assegnerà
ad essa una quantità di memoria pari alla somma della quantità di memoria
necessaria per i tipi elementari che essa contiene.
Di solito le dichiarazioni delle strutture compaiono all'inizio del sorgente, o comunque, in ogni caso, vanno dichiarate prima di essere utilizzate: solo dopo avere definito l'identificatore (può sembrare una ripetizione di quanto finora detto, ma è bene ribadirlo perché è un errore in cui molti incorrono) e il modello della struttura, come nell’esempio di poco fa, è possibile dichiarare ed utilizzare oggetti di quel tipo, vere e proprie variabili struct.
È possibile creare subito uno o più esemplari di strutture subito, al momento stesso della dichiarazione: è sufficiente mettere i nomi delle stesse tra la parentesi graffa } chiusa e il punto e virgola finale, separandoli con delle virgole:
struct indirizzo {
char nome[30];
char via[60];
char citta[20];
char stato[2];
int cap;
longint telefono;
} IndirizzoCliente, IndirizzoAmico, IndirizzoFornitore;
In questo modo, oltre a dichiarare la forma della struttura indirizzo, ne abbiamo creati subito 3 semplari: IndirizzoCliente, IndirizzoAmico, IndirizzoFornitore.
Se si sapesse a priori,
invece, che nell’ambito del programma si userà solo una struttura di un
determinato tipo, l'indentificatore della struttura (in questo caso
"indirizzo") sarebbe superfluo: Abbiamo così omesso l'identificatore
"indirizzo", creando solamente l'esemplare unico e irripetibile "IndirizzoCliente". Ora sappiamo come si
creano le strutture, vediamo come fare a leggere e scrivere nei suoi elementi... Questo avviene tramite
un operatore speciale del linguaggio C, l'operatore punto ".", che si
mette tra il nome della struttura e il nome dell'elemento: Con questa istruzione
scrivo nella variabile cap di IndirizzoCliente il valore 90124. Analogamente,
con IndirizzoCliente.via e IndirizzoCliente.citta accedo ai campi via e citta
nella struttura "IndirizzoCliente ", sia in lettura che in scrittura. In altri termini, si dice che si
è creato un riferimento a quel singolo elemento della struttura. Questo riferimento, così
creato, può essere passato come argomento ad una funzione, si possono fare
operazioni su di esso e essere indirizzato in maniera autonoma dal resto della
struttura. Vediamo qualche esempio
per capirci meglio. Nell’ipotetico
programma per il quale abbiamo già creato la struttura "indirizzo" e ne
abbiamo creato un’istanza di nome IndirizzoCliente potremmo aver
bisogno di operare all’interno dei singoli campi della struct. Ad esempio possiamo
visualizzare il contenuto del campo cap: Allo stesso modo,
possiamo scrivere nel campo nome con gets(): Si può persino
accedere ai singoli elementi degli array all'interno della struttura:< Come si vede la
notazione è la solita per gli array, ciò che cambia è quell’"
IndirizzoCliente" iniziale. Vediamo ora un utilizzo
semplice e compilabile di quanto detto fin qui.
Naturalmente in un caso
semplice come questo era più facile usare i 3 array e l’int "slegati"
senza dover andare a scomodare le strutture.
Ma noi possiamo ancora
spingerci oltre, componendo tra loro le singole strutture in modo da creare
anche degli array di strutture o persino delle strutture di strutture. Per quanto riguarda i
primi il modo di procedere è piuttosto banale: per definire un array di
strutture è necessario definire una struttura e poi dichiarare una array di
quel tipo. Così facendo abbiamo
creato un array di 200 strutture di tipo "indirizzo", il cui nome e'
IndirizziAmici, utilizzabile come una rubrica contenente al massimo 200
indirizzi. L’accesso ad un
determinato elemento di un particolare indirizzo all'interno di una rubrica così
strutturata è possibile in questo modo: Queste due istruzioni
stampano rispettivamente il nome del sesto indirizzo e la città del
ventiseiesimo (dal momento che, come sappiamo, il conteggio degli indici di un
array parte da 0). C’è da notare anche
che, se due variabili struttura sono del medesimo tipo, è anche possibile
assegnare l'una all'altra, ovvero copiare l'una nell'altra. Le assegnazioni sono
del tipo: Come dicevamo
all’inizio, gli elementi di una struttura possono essere di qualsiasi tipo di
dati valido in C, compresi gli array e le strutture stesse. Quindi è possibile
creare anche delle strutture nidificate, ossia delle strutture
all'interno di altre strutture. Vediamo come: In questo caso notiamo
che la struttura amici contiene tra i suoi elementi altre 2 strutture. Per accedere a elementi
"semplici"” come interi, float o array è sufficiente usare la basta usare la
solita sintassi col punto a ci siamo ormai abituati: Per accedere, invece,
agli elementi nidificati della struttura indirizzo, occorre: Ossia, ci si
riferisce agli elementi di ciascuna struttura a partire da sinistra verso destra
e dall'esterno all'interno, usando come connettivo l'operatore punto
".". L’unica limitazione imposta
dalle strutture nidificate è che (ovviamente) una struttura non potrà mai contenere
un’altra struttura avente il proprio stesso identificativo: per il compilatore
sarebbe impossibile risolvere completamente la definizione della struttura, in
quanto essa risulterebbe definita in funzione di se stessa. Mettiamo in pratica
quanto detto con un’altro esempio completo (e compilabile). È evidente come,
questo sistema a strutture, possa essere molto vantaggioso, sia per la
leggibilità del listato che per l'economia generale della gestione delle
risorse assegnate al programma, in tutti i casi in cui si devono gestire grosse
quantità di dati composti. Ulteriori vantaggi,
come vedremo nelle prossime lezioni, si potranno trarre associando alle
strutture i puntatori. Il concetto di union (unione) deriva direttamente da
quello di struttura, ma con una importante differenza: i campi di una union rappresentano diversi modi di vedere o, per meglio dire di rappresentare,
l'entità che la union stessa descrive. In altri termini mentre, come già sappiamo, i campi di una
struct descrivono, all’interno della stessa struttura, delle informazioni
differenti e, a ciascun campo, è assegnata dal compilatore una determinata
quantità di memoria, di modo che l’allocazione di memoria dell’intera
struttura è pari alla somma di quella allocata per ciascuno dei suoi campi, per
la union le cose vanno diversamente: per l’intera union viene allocata una
quantità di memoria condiviso dalle sue variabili membro e di dimensione pari
al più "grande" dei suoi membri. Questi, praticamente vengono
"sovrapposti", in quanto condividono la stessa memoria fisica. Una conseguenza di questo fatto è che inizializzando un
campo della union vengono inizializzati anche tutti gli altri campi. Anche le union trovano un "buon terreno" di
applicazione in relazione ai puntatori. Un ulteriore strumento che il C rende disponibile per
rappresentare più agevolmente dei gruppi di dati sono i tipi enum
(enumeratori) altresì detti costanti enumerative. Questi consentono di
descrivere con nomi simbolici gruppi di oggetti ai quali è possibile associare
valori numerici interi. Come si può vedere la dichiarazione di un enumeratore ricorda molto da
vicino quella di una struttura: anche in questo caso viene definito un modello;
la parola chiave questa volta è enum,
seguita anche in questo caso dal nome che si intende dare al modello di
enumeratore; vi sono le parentesi graffe aperta e chiusa, quest'ultima seguita
dal punto e virgola. La differenza più evidente rispetto alla dichiarazione di
una struttura consiste nel fatto che laddove in questo compaiono le
dichiarazioni dei campi (vere e proprie definizioni di variabili con tanto di
indicatore di tipo e punto e virgola), nella dichiarazione di enum
vi è un elenco dei nomi simbolici corrispondenti alle possibili manifestazioni
della qualità che l'enumeratore stesso rappresenta ma il cui valore rimane
costante. Da notare che il compilatore, di default, assegna anche dei
valori ai nomi simbolici elencati nel modello dell'enum: al primo
nome è associato il valore 0, al secondo 1, e così via. E' comunque possibile assegnare valori a piacere, purché
interi, ad uno o più nomi simbolici; ai restanti il valore viene assegnato
automaticamente dal compilatore, incrementando di uno il valore associato al
nome precedente. Nell'esempio, al nome lunedì è assegnato esplicitamente
valore 1:
il compilatore assegna valore 2 al nome martedì e così via. I valori
esplicitamente assegnati dal programmatore non devono necessariamente essere
consecutivi; la sola condizione da rispettare è che si tratti di valori interi.
struct {
char nome[30];
char via[60];
char citta[20];
char stato[2];
int cap;
long int telefono;
} IndirizzoCliente;
IndirizzoCliente.cap = 90124;
printf("Il cap e' %d", IndirizzoCliente.cap);
gets(IndirizzoCliente.nome);
IndirizzoCliente.nome[0] = "P";
IndirizzoCliente.nome[1] = "i";
IndirizzoCliente.nome[2] = "p";
IndirizzoCliente.nome[3] = "p";
IndirizzoCliente.nome[4] = "o";
#include <stdio.h>
struct indirizzo { /* Definiamo la struttura indirizzo*/
char nome[30];
char via[60];
char citta[20];
};
main() {
struct indirizzo IndirizzoCliente; /* Creiamo 1 esemplare della struttura
indirizzo di nome IndirizzoCliente */
/* Inseriamo i dati */
printf("Scrivi il tuo nome: ");
gets(IndirizzoCliente.nome); /* Input tramite gets */
printf("Scrivi la via: ");
gets(IndirizzoCliente.via);
printf("Citta': ");
gets(IndirizzoCliente.citta);
/* E adesso stampiamoli sullo schermo */
printf("\n\nTi chiami %s", IndirizzoCliente.nome);
printf("\nStai in via %s", IndirizzoCliente.via);
printf("Il tuo cap è %d", IndirizzoCliente.cap
printf("\nE abiti nella città di %s", IndirizzoCliente.citta);
/* Ora stampiamo un carattere solamente di un array nella struttura */
printf("\n\nE Il nome comincia per %c.\n", IndirizzoCliente.nome[0]);
}
struct indirizzo { /*Definiamo una struttura "indirizzo"*/
char nome[30];
char via[60];
char citta[20];
}; /*Senza crearne fisicamente ancora nessuna.*/
struct indirizzo IndirizziAmici[200];
printf("%s", IndirizziAmici[5].nome);
printf("%s", IndirizziAmici[25].citta);
struttura1 = struttura2;
IndirizziAmici[3] = IndirizziAmici[8];
struct amici {
struct indirizzo abitazione;
struct indirizzo lavoro;
int eta;
char PiattoPreferito[50];
};
Roberto.PiattoPreferito="Anatra all’arancia";
Roberto.abitazione.citta = "Palermo";
#include <stdio.h>
struct indirizzo { /* Definiamo una struttura "indirizzo" */
char nome[30];
char via[60];
char citta[20];
};
struct Amici { /* Definiamo una struttura "Amici" */
struct indirizzo abitazione;
char PiattoPreferito [60];
};
main() {
int i;
struct Amici AmiciPresenti[4]; /* Array di 4 strutture Amici */
for(i=0; i<3; i++) { /* Con il for richiedo 3
indirizzi da riempire */
printf("\nIndirizzo n.%d:",i);
printf("\nScrivi il tuo nome: ");
gets(AmiciPresenti[i].abitazione.nome); /* struttura nidificata */
printf("Scrivi la via: ");
gets(AmiciPresenti[i].abitazione.via); /* idem */
printf("Citta': ");
gets(AmiciPresenti[i].abitazione.citta); /* idem */
printf("Il tuo piatto preferito: ");
gets(AmiciPresenti[i].PiattoPreferito); /* array non nidificato */
}
/* Il quarto indirizzo lo ottengo copiandolo dal primo: */
AmiciPresenti[3] = AmiciPresenti[0]; /* Copia di strutture */
/* Stampiamo qualche dato qua e là per verifica */
printf("\n\nNome primo indirizzo: %s", AmiciPresenti[0].abitazione.nome);
printf("\nVia secondo indirizzo: %s", AmiciPresenti[1].abitazione.via);
printf("\nCittà terzo indirizzo: %s", AmiciPresenti[2].abitazione.citta);
printf("\nFrase famosa primo indirizzo: %s", AmiciPresenti[0].PiattoPreferito);
}
Le unioni
Gli enumeratori
Vediamo un esempio:
enum giorni {
lunedì,
martedì,
mercoledì,
giovedì,
venerdì,
sabato,
domenica
};
Anche la dichiarazione, l’inizializzazione e l’uso di una variabile di tipo enum
ricorda molto da vicino quella di una variabile struttura:
enum giorni oggi;
...
oggi = venerdì;
...
if(oggi == domenica)
printf("FESTIVO");
else if(oggi == sabato)
printf("PREFESTIVO");
else
printf("FERIALE");
enum giorni {
lunedì =1,
martedì,
mercoledì,
giovedì,
venerdì,
sabato,
domenica
};
Il vantaggio dell'uso degli enumeratori consiste nella semplicità di stesura e
nella migliore leggibilità del programma oltre che nella limitazione dei
possibili effetti collaterali legati all’utilizzo di variabili e costanti
esplicite.
Bibliografia
Testi per l'approfondimento
Questo articolo è stato scaricato dal
Club di informatica |